"""
@author: 江同学呀
@file: specialized_queries.py
@date: 2025/1/13 23:17
@desc:
    专用查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/specialized-queries.html

    This group contains queries which do not fit into the other groups:
    此组包含不适合其他组的查询：
"""
from typing import Union, Tuple, Dict, Iterable, List, Literal

from espc.common.common import Number
from espc.err.err import ESPCParamsError
from espc.orm.model.struct.functions import Linear, Sigmoid, Logarithm, Saturation
from espc.orm.model.dsl.queries.base_queries import _BaseQueries
from espc.orm.model.mapping.field.base_field.base_field import _BaseField
from espc.orm.model.model.base_document import BaseDocument
from espc.orm.model.model.base_index import BaseIndex
from espc.orm.model.scripting.script import Script as _Script
from espc.orm.model.text_analysis.analyzer import BaseAnalyzer


class DistanceFeature(_BaseQueries):
    """
    距离要素查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-distance-feature-query.html

    Boosts the relevance score of documents closer to a provided origin date or point. For example, you can use this
    query to give more weight to documents closer to a certain date or location.
    You can use the distance_feature query to find the nearest neighbors to a location. You can also use the query in a
    bool search’s should filter to add boosted relevance scores to the bool query’s scores.

    提高文档的相关性分数，使其更接近提供的源日期或点。例如，您可以使用此查询为更接近特定日期或位置的文档赋予更多权重。
    您可以使用 distance_feature 查询来查找某个位置最近的邻域。您还可以在 bool 搜索的 should 过滤器中使用查询，以将提升的相关性分数添加
    到 bool 查询的分数中。

    :param field:
        (Required, string) Name of the field used to calculate distances. This field must meet the following criteria:
            - Be a date, date_nanos or geo_point field
            - Have an index mapping parameter value of true, which is the default
            - Have an doc_values mapping parameter value of true, which is the default

        （必需，字符串）用于计算距离的字段的名称。此字段必须满足以下条件：
            - 是日期、date_nanos或geo_point字段
            - 索引映射参数值为 true，这是默认值
            - doc_values映射参数值为 true，这是默认值
    :param origin:
        (Required, string) Date or point of origin used to calculate distances.
        If the field value is a date or date_nanos field, the origin value must be a date. Date Math, such as now-1h,
        is supported.
        If the field value is a geo_point field, the origin value must be a geopoint.

        （必需，字符串）用于计算距离的日期或起始点。
        如果字段值是日期或date_nanos字段，则 origin 值必须为日期。支持日期数学，例如 now-1h。
        如果字段值是geo_point字段，则 origin 值必须为地理点。
    :param pivot:
        (Required, time unit or distance unit) Distance from the origin at which relevance scores receive half of the
        boost value.
        If the field value is a date or date_nanos field, the pivot value must be a time unit, such as 1h or 10d.
        If the field value is a geo_point field, the pivot value must be a distance unit, such as 1km or 12m.

        （必填，时间单位或距离单位）与相关性分数接收提升值一半的源的距离。
        如果字段值是日期或date_nanos字段，则透视值必须是时间单位，例如 1h 或 10d。
        如果字段值是 geo_point 字段，则枢轴值必须是距离单位，例如 1km 或 12m。
    :param boost:
        (Optional, float) Floating point number used to multiply the relevance score of matching documents. This value
        cannot be negative. Defaults to 1.0.
        （可选，浮点数）浮点数 用于将匹配文档的相关性分数相乘。此值不能为负数。默认为 1.0。
    """
    type: str = "distance_feature"

    def __init__(
            self, field: Union[str, _BaseField], origin: Union[str, Tuple[Number, Number]], pivot: str,
            boost: Number = None
    ):
        super().__init__()
        self._field: Union[str, _BaseField] = field
        self._origin: Union[str, Tuple[Number, Number]] = origin
        self._pivot: str = pivot
        self._boost: Number = boost
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "field": self._field if isinstance(self._field, str) else self._field._field_name,
            "origin": self._origin if isinstance(self._origin, str) else list(self._origin),
            "pivot": self._pivot,
        }
        if self._boost is not None:
            _body["boost"] = self._boost
        return _body


class MoreLikeThis(_BaseQueries):
    """
    更像是这个查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-mlt-query.html

    The More Like This Query finds documents that are "like" a given set of documents. In order to do so, MLT selects a
    set of representative terms of these input documents, forms a query using these terms, executes the query and
    returns the results. The user controls the input documents, how the terms should be selected and how the query is
    formed.
    The simplest use case consists of asking for documents that are similar to a provided piece of text. Here, we are
    asking for all movies that have some text similar to "Once upon a time" in their "title" and in their "description"
    fields, limiting the number of selected terms to 12.

    More Like This Query 查找与给定文档集“相似”的文档。为此，MLT 选择这些输入文档的一组代表性术语，使用这些术语形成查询，执行查询并返回结果。
    用户控制输入文档、应如何选择术语以及如何形成查询。
    最简单的用例包括请求与提供的文本类似的文档。在这里，我们要求所有在“标题”和“描述”字段中包含类似于“Once upon a time”文本的电影，并将所选术
    语的数量限制为 12 个。

    :param like:
        The only required parameter of the MLT query is like and follows a versatile syntax, in which the user can
        specify free form text and/or a single or multiple documents (see examples above). The syntax to specify
        documents is similar to the one used by the Multi GET API. When specifying documents, the text is fetched from
        fields unless overridden in each document request. The text is analyzed by the analyzer at the field, but could
        also be overridden. The syntax to override the analyzer at the field follows a similar syntax to the
        per_field_analyzer parameter of the Term Vectors API. Additionally, to provide documents not necessarily
        present in the index, artificial documents are also supported.

        MLT 查询的唯一必需参数是 like and follows 一种通用语法，在该语法中，用户可以指定自由格式的文本和/或单个或多个文档（请参阅上面的示
        例）。指定文档的语法类似于 Multi GET API 使用的语法。指定文档时，除非在每个文档请求中覆盖，否则将从字段中获取文本。文本由字段中的分析
        器分析，但也可以被覆盖。在字段中覆盖分析器的语法遵循与 Term Vectors API 的 per_field_analyzer 参数类似的语法。此外，为了提供索
        引中不一定存在的文档，还支持人工文档。
    :param unlike:
        The unlike parameter is used in conjunction with like in order not to select terms found in a chosen set of
        documents. In other words, we could ask for documents like: "Apple", but unlike: "cake crumble tree". The
        syntax is the same as like.

        unlike 参数与 like 结合使用，以便不选择在所选文档集中找到的术语。换句话说，我们可以要求提供类似 “Apple” 的文档，但不要像 “cake
        crumble tree” 这样的文档。语法与 like 相同。
    :param fields:
        A list of fields to fetch and analyze the text from. Defaults to the index.query.default_field index setting,
        which has a default value of *. The * value matches all fields eligible for term-level queries, excluding
        metadata fields.

        要从中获取和分析文本的字段列表。默认为 index.query.default_field 索引设置，其默认值为 *。该值 * 与符合术语级查询条件的所有字段
        匹配，不包括元数据字段。
    :param max_query_terms:
        The maximum number of query terms that will be selected. Increasing this value gives greater accuracy at the
        expense of query execution speed. Defaults to 25.
        将选择的查询词的最大数量。增加此值可以提高准确性，但会降低查询执行速度。默认值为 25。
    :param min_term_freq:
        The minimum term frequency below which the terms will be ignored from the input document. Defaults to 2.
        最小术语频率，低于该频率的术语将从输入文档中忽略。默认值为 2。
    :param min_doc_freq:
        The minimum document frequency below which the terms will be ignored from the input document. Defaults to 5.
        最小文档频率，低于该频率时，将从输入文档中忽略术语。默认值为 5。
    :param max_doc_freq:
        The maximum document frequency above which the terms will be ignored from the input document. This could be
        useful in order to ignore highly frequent words such as stop words. Defaults to unbounded (Integer.MAX_VALUE,
        which is 2^31-1 or 2147483647).
        最大文档频率，超过该频率时，将从输入文档中忽略术语。这对于忽略高度频繁的单词（如停用词）可能很有用。默认为无界（Integer.MAX_VALUE，
        即 2^31-1 或 2147483647）。
    :param min_word_length:
        The minimum word length below which the terms will be ignored. Defaults to 0.
        最小字长，低于该字长的项将被忽略。默认为 0。
    :param max_word_length:
        The maximum word length above which the terms will be ignored. Defaults to unbounded (0).
        最大字长，超过该字长时，术语将被忽略。默认为 unbounded （0）。
    :param stop_words:
        An array of stop words. Any word in this set is considered "uninteresting" and ignored. If the analyzer allows
        for stop words, you might want to tell MLT to explicitly ignore them, as for the purposes of document
        similarity it seems reasonable to assume that "a stop word is never interesting".
        停用词数组。此集合中的任何单词都被视为 “无趣” 并忽略。如果分析器允许停用词，您可能希望告诉 MLT 明确忽略它们，因为出于文档相似性的目的，
        假设“停用词从来都不有趣”似乎是合理的。
    :param analyzer:
        The analyzer that is used to analyze the free form text. Defaults to the analyzer associated with the first
        field in fields.
        用于分析自由格式文本的分析器。默认为与 fields 中的第一个字段关联的分析器。
    :param minimum_should_match:
        After the disjunctive query has been formed, this parameter controls the number of terms that must match. The
        syntax is the same as the minimum should match. (Defaults to "30%").
        形成析取查询后，此参数控制必须匹配的术语数。语法与 minimum should match 相同。（默认为“30%”）。
    :param fail_on_unsupported_field:
        Controls whether the query should fail (throw an exception) if any of the specified fields are not of the
        supported types (text or keyword). Set this to false to ignore the field and continue processing.
        Defaults to true.
        控制如果任何指定的字段不是支持的类型（text 或 keyword），则查询是否应失败（引发异常）。将此项设置为 false 可忽略该字段并继续处理。
        默认为 true。
    :param boost_terms:
        Each term in the formed query could be further boosted by their tf-idf score. This sets the boost factor to use
        when using this feature. Defaults to deactivated (0). Any other positive value activates terms boosting with
        the given boost factor.
        已形成查询中的每个词都可以通过其 tf-idf 分数进一步提升。这将设置使用此功能时要使用的增强因子。默认为 deactivated （0）。任何其他正值
        都会激活具有给定提升因子的术语提升。
    :param include:
        Specifies whether the input documents should also be included in the search results returned. Defaults to false.
        指定是否还应将输入文档包含在返回的搜索结果中。默认为 false。
    :param boost:
        Sets the boost value of the whole query. Defaults to 1.0.
        设置整个查询的提升值。默认为 1.0。
    """
    type: str = "more_like_this"

    def __init__(
            self, fields: Union[str, Iterable[Union[str, _BaseField]]] = None,
            like: List[Union[Dict, BaseDocument]] = None, unlike: List[Union[Dict, BaseDocument]] = None,
            max_query_terms: int = None, min_term_freq: int = None,
            min_doc_freq: int = None, max_doc_freq: int = None, min_word_length: int = None,
            max_word_length: int = None, stop_words: List[str] = None, analyzer: Union[str, BaseAnalyzer] = None,
            minimum_should_match: Union[str, int] = None, fail_on_unsupported_field: bool = None,
            boost_terms: Number = None, include: bool = None, boost: Number = None
    ):
        super().__init__()
        self._fields: Union[str, Iterable[Union[str, _BaseField]]] = fields
        self._like: List[Union[Dict, BaseDocument]] = like
        self._unlike: List[Union[Dict, BaseDocument]] = unlike
        self._max_query_terms: int = max_query_terms
        self._min_term_freq: int = min_term_freq
        self._min_doc_freq: int = min_doc_freq
        self._max_doc_freq: int = max_doc_freq
        self._min_word_length: int = min_word_length
        self._max_word_length: int = max_word_length
        self._stop_words: List[str] = stop_words
        self._analyzer: Union[str, BaseAnalyzer] = analyzer
        self._minimum_should_match: Union[str, int] = minimum_should_match
        self._fail_on_unsupported_field: bool = fail_on_unsupported_field
        self._boost_terms: Number = boost_terms
        self._include: bool = include
        self._boost: Number = boost
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "fields": (
                [
                    field if isinstance(field, str) else field._field_name for field in self._fields
                ]
                if not isinstance(self._fields, str) else self._fields
            ),
        }
        if self._like:
            _body["like"] = [doc if isinstance(doc, Dict) else doc.serialize() for doc in self._like]
        if self._unlike:
            _body["unlike"] = [doc if isinstance(doc, Dict) else doc.serialize() for doc in self._unlike]
        if self._max_query_terms is not None:
            _body["max_query_terms"] = self._max_query_terms
        if self._min_term_freq is not None:
            _body["min_term_freq"] = self._min_term_freq
        if self._min_doc_freq is not None:
            _body["min_doc_freq"] = self._min_doc_freq
        if self._max_doc_freq is not None:
            _body["max_doc_freq"] = self._max_doc_freq
        if self._min_word_length is not None:
            _body["min_word_length"] = self._min_word_length
        if self._max_word_length is not None:
            _body["max_word_length"] = self._max_word_length
        if self._stop_words:
            _body["stop_words"] = self._stop_words
        if self._analyzer:
            _body["analyzer"] = self._analyzer if isinstance(self._analyzer, str) else self._analyzer._build()
        if self._minimum_should_match is not None:
            _body["minimum_should_match"] = self._minimum_should_match
        if self._fail_on_unsupported_field is not None:
            _body["fail_on_unsupported_field"] = self._fail_on_unsupported_field
        if self._boost_terms is not None:
            _body["boost_terms"] = self._boost_terms
        if self._include is not None:
            _body["include"] = self._include
        if self._boost is not None:
            _body["boost"] = self._boost
        return _body


class Percolate(_BaseQueries):
    """
    渗透查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-percolate-query.html

    The percolate query can be used to match queries stored in an index. The percolate query itself contains the
    document that will be used as query to match with the stored queries.
    percolate 查询可用于匹配存储在索引中的查询。percolate 查询本身包含将用作查询以匹配存储查询的文档。

    :param field:
        The field of type percolator that holds the indexed queries. This is a required parameter.
        保存索引查询的 percolator 类型的字段。这是一个必需参数。
    :param name:
        The suffix to be used for the _percolator_document_slot field in case multiple percolate queries have been
        specified. This is an optional parameter.
        如果指定了多个渗透查询，则用于 _percolator_document_slot 字段的后缀。这是一个可选参数。
    :param document:
        The source of the document being percolated.
        正在渗透的文档的源。
    :param documents:
        Like the document parameter, but accepts multiple documents via a json array.
        与 document 参数类似，但通过 json 数组接受多个文档。
    :param document_type:
        The type / mapping of the document being percolated. This parameter is deprecated and will be removed in
        Elasticsearch 8.0.
        正在渗透的文档的类型/映射。此参数已弃用，并将在 Elasticsearch 8.0 中删除。
    :param index:
        The index the document resides in. This is a required parameter.
        文档所在的索引。这是一个必需的参数。
    :param type_:
        The type of the document to fetch. This parameter is deprecated and will be removed in Elasticsearch 8.0.
        要获取的文档的类型。此参数已弃用，并将在 Elasticsearch 8.0 中删除。
    :param id_:
        The id of the document to fetch. This is a required parameter.
        要获取的文档的 ID。这是一个必需的参数。
    :param routing:
        Optionally, routing to be used to fetch document to percolate.
        （可选）用于获取要渗透的文档的路由。
    :param preference:
        Optionally, preference to be used to fetch document to percolate.
        （可选）用于获取要渗透的文档的首选项。
    :param version:
        Optionally, the expected version of the document to be fetched.
        （可选）要获取的文档的预期版本。
    """
    type: str = "percolate"

    def __init__(
            self, field: Union[str, _BaseField], name: str = None,
            document: Union[BaseDocument, Dict] = None, documents: Iterable[Union[BaseDocument, Dict]] = None,
            document_type: str = None, index: Union[str, BaseIndex] = None, type_: str = None, id_: str = None,
            routing: str = None, preference: str = None, version: int = None
    ):
        super().__init__()
        self._field: Union[str, _BaseField] = field
        self._name: str = name
        self._document: Union[BaseDocument, Dict] = document
        self._documents: Iterable[Union[BaseDocument, Dict]] = documents
        self._document_type: str = document_type
        self._index: Union[str, BaseIndex] = index
        self._type: str = type_
        self._id: str = id_
        self._routing: str = routing
        self._preference: str = preference
        self._version: int = version
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "field": self._field if isinstance(self._field, str) else self._field._field_name
        }
        if self._name:
            _body["name"] = self._name
        if self._document:
            _body["document"] = self._document if isinstance(self._document, Dict) else self._document.serialize()
        if self._documents:
            _body["documents"] = [
                doc if isinstance(doc, Dict) else doc.serialize()
                for doc in self._documents
            ]
        if self._document_type:
            _body["document_type"] = self._document_type
        if self._index:
            _body["index"] = self._index if isinstance(self._index, str) else self._index.index_name
        if self._type:
            _body["type"] = self._type
        if self._id:
            _body["id"] = self._id
        if self._routing:
            _body["routing"] = self._routing
        if self._preference:
            _body["preference"] = self._preference
        if self._version is not None:
            _body["version"] = self._version
        return _body


class RankFeature(_BaseQueries):
    """
    对特征查询进行排名
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-rank-feature-query.html

    Boosts the relevance score of documents based on the numeric value of a rank_feature or rank_features field.
    The rank_feature query is typically used in the should clause of a bool query so its relevance scores are added to
    other scores from the bool query.
    With positive_score_impact set to false for a rank_feature or rank_features field, we recommend that every document
    that participates in a query has a value for this field. Otherwise, if a rank_feature query is used in the should
    clause, it doesn’t add anything to a score of a document with a missing value, but adds some boost for a document
    containing a feature. This is contrary to what we want – as we consider these features negative, we want to rank
    documents containing them lower than documents missing them.
    Unlike the function_score query or other ways to change relevance scores, the rank_feature query efficiently skips
    non-competitive hits when the track_total_hits parameter is not true. This can dramatically improve query speed.

    根据 rank_feature 或 rank_features 字段的数值提高文档的相关性分数。
    rank_feature 查询通常用于 bool 查询的 should 子句中，因此其相关性分数将添加到 bool 查询中的其他分数。
    将 rank_feature 或 rank_features 字段的 positive_score_impact 设置为 false 后，我们建议参与查询的每个文档都有此字段的值。否则，如果在 should 子句中使用rank_feature查询，它不会向缺少值的文档的分数添加任何内容，但会为包含功能的文档增加一些提升。这与我们想要的相反 – 由于我们认为这些功能是负面的，因此我们希望将包含这些功能的文档排名低于缺少这些功能的文档。
    与function_score查询或更改相关性分数的其他方法不同，当 track_total_hits 参数不为 true 时，rank_feature查询会有效地跳过非竞争性点击。这可以显著提高查询速度。

    :param field:
        (Required, string) rank_feature or rank_features field used to boost relevance scores.
        （必需，字符串）用于提高相关性分数的 rank_feature 或 rank_features 字段。
    :param boost:
        (Optional, float) Floating point number used to decrease or increase relevance scores. Defaults to 1.0.
        Boost values are relative to the default value of 1.0. A boost value between 0 and 1.0 decreases the relevance
        score. A value greater than 1.0 increases the relevance score.

        （可选，浮点数）用于降低或提高相关性分数的浮点数。默认为 1.0。
        提升值是相对于默认值 1.0 的。介于 0 和 1.0 之间的提升值会降低相关性分数。大于 1.0 的值会增加相关性分数。
    :param saturation:
        (Optional, function object) Saturation function used to boost relevance scores based on the value of the rank
        feature field. If no function is provided, the rank_feature query defaults to the saturation function. See
        Saturation for more information.
        Only one function saturation, log, sigmoid or linear can be provided.

        （可选，函数对象）饱和度函数，用于根据排名特征字段的值提高相关性分数。如果未提供任何函数，则rank_feature查询默认为 saturation 函数。
        请参阅 饱和度 以了解更多信息。
        只能提供一个函数 saturation， log， sigmoid 或 linear.
    :param log:
        (Optional, function object) Logarithmic function used to boost relevance scores based on the value of the rank
        feature field. See Logarithm for more information.
        Only one function saturation, log, sigmoid or linear can be provided.

        （可选，函数对象）用于根据排名特征字段的值提高相关性分数的对数函数。有关更多信息，请参阅 Logarithm 。
        只能提供一个函数 saturation， log， sigmoid 或 linear.
    :param sigmoid:
        (Optional, function object) Sigmoid function used to boost relevance scores based on the value of the rank
        feature field. See Sigmoid for more information.
        Only one function saturation, log, sigmoid or linear can be provided.

        （可选，函数对象）Sigmoid 函数，用于根据排名特征字段的值提高相关性分数。有关更多信息，请参阅 Sigmoid。
        只能提供一个函数 saturation， log， sigmoid 或 linear.
    :param linear:
        (Optional, function object) Linear function used to boost relevance scores based on the value of the rank
        feature field. See Linear for more information.
        Only one function saturation, log, sigmoid or linear can be provided.

        （可选，函数对象）用于根据 rank feature 字段的值提高相关性分数的线性函数。请参阅 Linear 以了解更多信息。
        只能提供一个函数 saturation， log， sigmoid 或 linear。
    """
    type: str = "rank_feature"

    def __init__(
            self, field: Union[str, _BaseField], boost: Number = None,
            saturation: Union[Saturation, Dict] = None, log: Union[Logarithm, Dict] = None,
            sigmoid: Union[Sigmoid, Dict] = None, linear: Union[Linear, Dict] = None,
    ):
        if sum(param is not None for param in (saturation, log, sigmoid, linear)) > 1:
            raise ESPCParamsError("函数 saturation，log，sigmoid 和 linear 只能提供其中一个")
        super().__init__()
        self._field: Union[str, _BaseField] = field
        self._boost: Number = boost
        self._saturation: Union[Saturation, Dict] = saturation
        self._log: Union[Logarithm, Dict] = log
        self._sigmoid: Union[Sigmoid, Dict] = sigmoid
        self._linear: Union[Linear, Dict] = linear
        return

    def _build(self) -> Dict:
        _body: Dict = {}
        if self._field:
            _body["field"] = self._field if isinstance(self._field, str) else self._field._field_name
        if self._boost is not None:
            _body["boost"] = self._boost
        if self._saturation:
            _body["saturation"] = self._saturation if isinstance(self._saturation, Dict) else self._saturation._build()
        elif self._log:
            _body["log"] = self._log if isinstance(self._log, Dict) else self._log._build()
        elif self._sigmoid:
            _body["sigmoid"] = self._sigmoid if isinstance(self._sigmoid, Dict) else self._sigmoid._build()
        elif self._linear:
            _body["linear"] = self._linear if isinstance(self._linear, Dict) else self._linear._build()
        return _body


class Script(_BaseQueries):
    """
    脚本查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-script-query.html

    Runtime fields provide a very similar feature that is more flexible. You write a script to create field values and
    they are available everywhere, such as fields, all queries, and aggregations.
    Filters documents based on a provided script. The script query is typically used in a filter context.
    Using scripts can result in slower search speeds. See Scripts, caching, and search speed.

    运行时字段提供了非常相似的功能，但更加灵活。您可以编写脚本来创建字段值，这些值随处可用，例如字段、所有查询和聚合。
    根据提供的脚本筛选文档。脚本查询通常用于筛选条件上下文。
    使用脚本可能会导致搜索速度变慢。请参阅脚本、缓存和搜索速度。

    :param script:
        (Required, script object) Contains a script to run as a query. This script must return a boolean value, true or
        false.
        （必需，脚本对象）包含要作为查询运行的脚本。此脚本必须返回布尔值 true 或 false。
    """
    type: str = "script"

    def __init__(self, script: Union[_Script, Dict]):
        super().__init__()
        self._script: Union[Script, Dict] = script
        return

    def _build(self) -> Dict:
        return {
            "script": self._script if isinstance(self._script, Dict) else self._script._build()
        }


class ScriptScore(_BaseQueries):
    """
    脚本分数查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-script-score-query.html

    Uses a script to provide a custom score for returned documents.
    The script_score query is useful if, for example, a scoring function is expensive and you only need to calculate
    the score of a filtered set of documents.

    使用脚本为返回的文档提供自定义分数。
    例如，如果评分函数成本高昂，并且您只需要计算一组筛选文档的分数，则 script_score 查询非常有用。

    :param query:
        (Required, query object) Query used to return documents.
        （必需，查询对象）用于返回文档的查询。
    :param script:
        (Required, script object) Script used to compute the score of documents returned by the query.
        Final relevance scores from the script_score query cannot be negative. To support certain search optimizations,
        Lucene requires scores be positive or 0.

        （必需，脚本对象）用于计算查询返回的文档分数的脚本。
        script_score查询的最终相关性分数不能为负数。为了支持某些搜索优化，Lucene 要求 scores 为正数或 0。
    :param min_score:
        (Optional, float) Documents with a score lower than this floating point number are excluded from the search
        results.
        （可选，浮点数）分数低于此浮点数的文档将从搜索结果中排除。
    :param boost:
        (Optional, float) Documents' scores produced by script are multiplied by boost to produce final documents'
        scores. Defaults to 1.0.
        （可选，浮点）脚本生成的文档分数乘以 boost 以生成最终文档的分数。默认值为 1.0。
    """
    type: str = "script_score"

    def __init__(
            self, query: Union[_BaseQueries, Dict], script: Union[_Script, Dict], min_score: Number = None,
            boost: Number = None
    ):
        super().__init__()
        self._query: Union[_BaseQueries, Dict] = query
        self._script: Union[_Script, Dict] = script
        self._min_score: Number = min_score
        self._boost: Number = boost
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "query": self._query if isinstance(self._query, Dict) else self._query._build(),
            "script": self._script if isinstance(self._script, Dict) else self._script._build()
        }
        if self._min_score is not None:
            _body["min_score"] = self._min_score
        if self._boost is not None:
            _body["boost"] = self._boost
        return _body


class Wrapper(_BaseQueries):
    """
    包装器查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-wrapper-query.html

    A query that accepts any other query as base64 encoded string.
    接受任何其他查询作为 base64 编码字符串的查询。
    """
    type: str = "wrapper"

    def __init__(self, query: str):
        super().__init__()
        self._query: str = query
        return

    def _build(self) -> Dict:
        return {
            "query": self._query
        }


class Pinned(_BaseQueries):
    """
    固定查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-pinned-query.html

    Promotes selected documents to rank higher than those matching a given query. This feature is typically used to
    guide searchers to curated documents that are promoted over and above any "organic" matches for a search. The
    promoted or "pinned" documents are identified using the document IDs stored in the _id field.
    将所选文档的排名提升到高于与给定查询匹配的文档的排名。此功能通常用于引导搜索者找到精选文档，这些文档被提升到搜索的任何“自然”匹配项之上。
    提升或“固定”的文档使用存储在 _id 字段中的文档 ID 进行标识。
    :param ids:
        (Optional, array) Document IDs listed in the order they are to appear in results. Required if docs is not
        specified.
        （可选，数组）按文档 ID 在结果中出现的顺序列出。如果未指定 docs，则为必需。
    :param docs:
        (Optional, array) Documents listed in the order they are to appear in results. Required if ids is not specified.
        You can specify the following attributes for each document:
            - _id
                (Required, string) The unique document ID.
            - _index
                (Required, string) The index that contains the document.

        （可选，数组）按文档在结果中的显示顺序列出。如果未指定 ids，则为必需。您可以为每个文档指定以下属性：
            - _id
                （必需，字符串）唯一的文档 ID。
            - _index
                （必需，字符串）包含文档的索引。
    :param organic:
        Any choice of query used to rank documents which will be ranked below the "pinned" documents.
        用于对文档进行排名的查询选项，这些文档将排在“固定”文档之下。
    """
    type: str = "pinned"

    def __init__(
            self, ids: List[str] = None, docs: List[Dict[Literal["_id", "_index"], str]] = None,
            organic: Union[_BaseQueries, Dict] = None
    ):
        super().__init__()
        self._ids: List[str] = ids
        self._docs: List[Dict[Literal["_id", "_index"], str]] = docs
        self._organic: Union[_BaseQueries, Dict] = organic
        return

    def _build(self) -> Dict:
        _body: Dict = {}
        if self._ids:
            _body["ids"] = self._ids
        if self._docs:
            _body["docs"] = self._docs
        if self._organic:
            _body["organic"] = self._organic if isinstance(self._organic, Dict) else self._organic._build()
        return _body



